﻿using System;
using System.Threading;

namespace PhoneNetTools
{
    /// <summary>
    /// This is a generic implementation of the <see cref="IAsyncResult"/> interface for WP7
    /// based on an MSDN magazine article by Jeffrey Richter:
    /// http://msdn.microsoft.com/en-us/magazine/cc163467.aspx
    /// </summary>
    /// <typeparam name="T">The result type.</typeparam>
    internal class AsyncResult<T> : IAsyncResult
    {
        private readonly AsyncCallback _asyncCallback;
        private readonly object _asyncState;

        // consts for the state (to be used with Interlocked.Exchange)
        private const int StatePending = 0;
        private const int StateCompletedSynchronously = 1;
        private const int StateCompletedAsynchronously = 2;
        private volatile int _completedState = StatePending;

        // the result of the operation
        private T _result = default(T);

        // creation of the wait handle is deferred for performance reasons
        private ManualResetEvent _asyncWaitHandle;

        // contains the original exception in case of an error
        private Exception _exception;

        /// <summary>
        /// Initializes a new instance of the <see cref="AsyncResult&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="asyncCallback">The async callback to invoke when the operation is finished.</param>
        /// <param name="state">The state to pass on to the async callback.</param>
        public AsyncResult(AsyncCallback asyncCallback, object state)
        {
            _asyncCallback = asyncCallback;
            _asyncState = state;
        }

        /// <summary>
        /// Sets the <c>AsyncResult</c> as completed using the given result or exception.
        /// </summary>
        /// <param name="result">The result to use. Can be <c>null</c> in case of an error.</param>
        /// <param name="exception">The exception to use. Typically is <c>null</c> unless an error occurred.</param>
        /// <param name="completedSynchronously">if set to <c>true</c> indicates that the operation completed synchronously.</param>
        public void SetAsCompleted(T result, Exception exception, bool completedSynchronously)
        {
            // store the result and exception
            _result = result;
            _exception = exception;

            // set the state
            var prevState = Interlocked.Exchange(ref _completedState, completedSynchronously ? StateCompletedSynchronously : StateCompletedAsynchronously);
            if (prevState != StatePending)
            {
                throw new InvalidOperationException("You can set a result only once");
            }

            // if the wait handle is created, set it
            // this uses the field, not the property, to avoid creation of the handle if this option is not used
            if (_asyncWaitHandle != null)
            {
                _asyncWaitHandle.Set();
            }

            // call the callback if it was set
            if (_asyncCallback != null)
            {
                _asyncCallback(this);
            }
        }

        /// <summary>
        /// Ends the operation and returns the result, or throws any pending exceptions that occurred during the operation.
        /// </summary>
        /// <returns>The result of the operation.</returns>
        public T EndInvoke()
        {
            // this method assumes that only one thread calls EndInvoke for this object
            if (!IsCompleted)
            {
                // if the operation isn't done, wait for it
                AsyncWaitHandle.WaitOne();
                AsyncWaitHandle.Close();
                _asyncWaitHandle = null;
            }

            // if an exception occured during the operation, throw it now...
            if (_exception != null)
            {
                throw _exception;
            }

            // ... or return the result
            return _result;
        }

        #region Implementation of IAsyncResult

        /// <summary>
        /// Gets a user-defined object that qualifies or contains information about an asynchronous operation.
        /// </summary>
        /// <returns>A user-defined object that qualifies or contains information about an asynchronous operation.</returns>
        public object AsyncState
        {
            get
            {
                return _asyncState;
            }
        }

        /// <summary>
        /// Gets a value that indicates whether the asynchronous operation completed synchronously.
        /// </summary>
        /// <returns>true if the asynchronous operation completed synchronously; otherwise, false.</returns>
        public bool CompletedSynchronously
        {
            get
            {
                return _completedState == StateCompletedSynchronously;
            }
        }

        /// <summary>
        /// Gets a value that indicates whether the asynchronous operation has completed.
        /// </summary>
        /// <returns>true if the operation is complete; otherwise, false.</returns>
        public bool IsCompleted
        {
            get
            {
                return _completedState != StatePending;
            }
        }

        /// <summary>
        /// Gets a <see cref="T:System.Threading.WaitHandle"/> that is used to wait for an asynchronous operation to complete.
        /// </summary>
        /// <returns>A wait handle that is used to wait for an asynchronous operation to complete.</returns>
        public WaitHandle AsyncWaitHandle
        {
            get
            {
                if (_asyncWaitHandle == null)
                {
                    var done = IsCompleted;
                    var mre = new ManualResetEvent(done);
                    if (Interlocked.CompareExchange(ref _asyncWaitHandle, mre, null) != null)
                    {
                        // Another thread created this object's event; dispose 
                        // the event we just created
                        mre.Close();
                    }
                    else
                    {
                        if (!done && IsCompleted)
                        {
                            // If the operation wasn't done when we created 
                            // the event but now it is done, set the event
                            _asyncWaitHandle.Set();
                        }
                    }
                }
                return _asyncWaitHandle;
            }
        }

        #endregion
    }
}
